import { Entity } from "../Core/Module/Entity/Entity";
import { SceneType } from "../Core/Module/Entity/SceneType";
import { ITypeBind } from "../Core/Object/NamedObject";
import { Dictionary } from "../Support/Share/Lib/Dictionary";

/******************************************************************************************************
 * 
 * 
 * 
 *****************************************************************************************************/

interface Dict<T> {
    [x: number | string]: T;
}

/******************************************************************************************************
 * 
 * 
 * 
 *****************************************************************************************************/

export type Type = string;

/******************************************************************************************************
 * 
 * 
 * 
 *****************************************************************************************************/

interface Property {
    propertyKey: string;
    descriptor: PropertyDescriptor;
}

export interface BaseAttrData {

}

export class BaseAttrData {

}

export type ClassConstructor = { new(...args: any[]): any };

export type TypeClassConstructor<T> = { new(...args: any[]): T };

/******************************************************************************************************
 * 
 * 
 * 
 *****************************************************************************************************/

export function IsAssignableFrom(target: Function, source: Function): boolean {
    return target.prototype instanceof source;
}

/******************************************************************************************************
 * 
 * 
 * 
 *****************************************************************************************************/

const BsonIngoreListKey = "bsonIngoreList";

function Add2BsonIngore(object: Object, propertyName: string) {
    if (!object[BsonIngoreListKey]) {
        object[BsonIngoreListKey] = [];
        Object.defineProperty(object, BsonIngoreListKey, { enumerable: false });
    }
    object[BsonIngoreListKey].push(propertyName);
}

function GetBsonIngore(object: Object): string[] {
    let bsonIngoreList: string[] = [];
    if (object[BsonIngoreListKey]) {
        bsonIngoreList = object[BsonIngoreListKey];
    }
    return bsonIngoreList;
}

export function BsonIgnore() {
    return function (object: Object, propertyName: string) {
        Add2BsonIngore(object, propertyName);
    };
}

export function Object2Bson<T extends Entity>(object: Object) {
    let newObject = {};
    let list = GetBsonIngore(object);
    for (let key in object) {
        if (list.indexOf(key) != -1) {
            continue;
        }
        if (key === "_Id" || key === "_id") {
            key = "Id";
        }
        newObject[key] = object[key];
    }
    return newObject as T;
}

export function ObjectList2BsonList(list: Object[]) {
    let bsonList = [];
    for (let key in list) {
        let doc = list[key];
        let bson = Object2Bson(doc);
        bsonList.push(bson);
    }
    return bsonList;
}

/******************************************************************************************************
 * 
 * 
 * 
 *****************************************************************************************************/

let customAttributes: Dict<BaseAttrData[]> = {};

export function GetCustomAttributes<T extends ClassConstructor>(baseClazz: T) {
    let type = baseClazz.name;
    if (customAttributes[type]) {
        return customAttributes[type];
    }
    return [];
}

export function AddCustomAttribute(type: string, attrData: BaseAttrData) {
    if (!customAttributes[type]) {
        customAttributes[type] = [];
    }
    customAttributes[type].push(attrData);
}

/******************************************************************************************************
 * 
 * 
 * 
 *****************************************************************************************************/

export function ValidParam<T>(t: T) {
    if (t === null || t === undefined) {
        return false;
    }
    return true;
}

export function DumpObject(object) {
    console.log(`=====DumpObject=====`);
    for (let key in object) {
        console.log(key);
        console.log(object[key]);
    }
    console.log(`=====DumpObject=====`);
}

/******************************************************************************************************
 * 
 * 
 * 
 *****************************************************************************************************/

export type BufferData = Blob | ArrayBuffer;

export interface IMessageSerializeData {
    Type: string;
    Parent: string;
}

export interface EventAttrData extends BaseAttrData {
    SceneType: SceneType;
}

export class EventAttrData extends BaseAttrData {
    SceneType: SceneType;
}

export interface InvokeAttrData extends BaseAttrData {
    Type: number;
}

export class InvokeAttrData extends BaseAttrData {
    Type: number;
}

export interface MessageAttrData extends BaseAttrData {
    Opcode: number;
}

export class MessageAttrData extends BaseAttrData {
    Opcode: number;
}

export interface ResponseTypeAttrData extends BaseAttrData {
    Type: string;
}

export class ResponseTypeAttrData extends BaseAttrData {
    Type: string;
}

export interface MessageHandlerAttrData extends BaseAttrData {
    SceneType: SceneType;
}

export class MessageHandlerAttrData extends BaseAttrData {
    SceneType: SceneType;
}

export interface ActorMessageHandlerAttrData extends BaseAttrData {
    SceneType: SceneType;
}

export class ActorMessageHandlerAttrData extends BaseAttrData {
    SceneType: SceneType;
}

export interface ActionAttrData extends BaseAttrData {

}

export class ActionAttrData extends BaseAttrData {

}

export interface HttpHandlerAttrData extends BaseAttrData {
    SceneType: SceneType;
    Path: string;
}

export class HttpHandlerAttrData extends BaseAttrData {
    SceneType: SceneType;
    Path: string;
}

export interface ConsoleHandlerAttrData extends BaseAttrData {
    Mode: string;
}

export class ConsoleHandlerAttrData extends BaseAttrData {
    Mode: string;
}

/******************************************************************************************************
 * 
 * 
 * 
 *****************************************************************************************************/

let baseAttributeDict = new Dictionary<string, any>();

export function GetAssemblyTypes() {
    return baseAttributeDict;
}

export function BaseAttribute(type: string, baseClazz, typeClazz?) {
    if (!baseAttributeDict[type]) {
        baseAttributeDict[type] = [];
    }
    baseAttributeDict[type].push(baseClazz);

    let basePrototype = baseClazz.prototype as ITypeBind;
    if (typeClazz) {
        basePrototype.SetupType && basePrototype.SetupType(typeClazz);
    } else {
        basePrototype.SetupType && basePrototype.SetupType(baseClazz);
    }
}

export function ObjectSystemAttribute() {
    return function (baseClazz) {
        BaseAttribute(GetTypeOf(ObjectSystemAttribute), baseClazz);
    }
}

export function MessageAttribute(Opcode: number) {
    return function (baseClazz) {
        BaseAttribute(GetTypeOf(MessageAttribute), baseClazz);

        let attrType = baseClazz.name;
        let attrData = new MessageAttrData();
        attrData.Opcode = Opcode;
        AddCustomAttribute(attrType, attrData);
    }
}

export function ResponseTypeAttribute(typeClazz) {
    return function (baseClazz) {
        BaseAttribute(GetTypeOf(ResponseTypeAttribute), baseClazz, typeClazz);

        let attrType = baseClazz.name;
        let attrData = new ResponseTypeAttrData();
        attrData.Type = typeClazz.name;
        AddCustomAttribute(attrType, attrData);
    }
}

export function MessageIMAttribute(baseClazz, request, response) {
    let basePrototype = baseClazz.prototype;
    if (request) {
        basePrototype.SetupMessageType && basePrototype.SetupMessageType(request);
    }
    if (response) {
        basePrototype.SetupResponseType && basePrototype.SetupResponseType(response);
    }
}

export function MessageHandlerAttribute<T, K>(sceneType: SceneType = SceneType.None, request?: T, response?: K) {
    return function (baseClazz) {
        BaseAttribute(GetTypeOf(MessageHandlerAttribute), baseClazz);
        MessageIMAttribute(baseClazz, request, response);

        let attrType = baseClazz.name;
        let attrData = new MessageHandlerAttrData();
        attrData.SceneType = sceneType;
        AddCustomAttribute(attrType, attrData);
    }
}

export function ActorMessageIMAttribute(baseClazz, request, response) {
    let basePrototype = baseClazz.prototype;
    if (request) {
        basePrototype.SetupRequestType && basePrototype.SetupRequestType(request);
    }
    if (response) {
        basePrototype.SetupResponseType && basePrototype.SetupResponseType(response);
    }
}

export function ActorMessageHandlerAttribute<T, K>(sceneType: SceneType = SceneType.None, request?: T, response?: K) {
    return function (baseClazz) {
        BaseAttribute(GetTypeOf(ActorMessageHandlerAttribute), baseClazz);
        ActorMessageIMAttribute(baseClazz, request, response);

        let attrType = baseClazz.name;
        let attrData = new ActorMessageHandlerAttrData();
        attrData.SceneType = sceneType;
        AddCustomAttribute(attrType, attrData);
    }
}

export function EventAttribute(typeClazz: ClassConstructor, sceneType: SceneType = SceneType.None) {
    return function (baseClazz) {
        BaseAttribute(GetTypeOf(EventAttribute), baseClazz, typeClazz);

        let attrType = baseClazz.name;
        let attrData = new EventAttrData();
        attrData.SceneType = sceneType;
        AddCustomAttribute(attrType, attrData);
    }
}

export function HttpHandlerAttribute(sceneType: SceneType, path: string) {
    return function (baseClazz) {
        BaseAttribute(GetTypeOf(HttpHandlerAttribute), baseClazz);

        let attrType = baseClazz.name;
        let attrData = new HttpHandlerAttrData();
        attrData.SceneType = sceneType;
        attrData.Path = path;
        AddCustomAttribute(attrType, attrData);
    }
}

let invokeBaseDict = new Dictionary<string, any>();

function GetParentClazz(baseClazz) {
    let parentClazz = Object.getPrototypeOf(baseClazz);
    if (parentClazz) {
        let type = GetTypeOf(parentClazz);
        if (invokeBaseDict && invokeBaseDict[type]) {
            return invokeBaseDict[type];
        }
    }
    return null;
}

export function InvokeBaseAttribute<T extends ClassConstructor>(typeClazz: T) {
    return function (baseClazz) {
        invokeBaseDict[baseClazz.name] = typeClazz;
    };
}

export function InvokeAttribute<T extends ClassConstructor>(type: number = 0, typeClazz?: T) {
    return function (baseClazz) {
        if (!typeClazz) {
            typeClazz = GetParentClazz(baseClazz);
        }
        BaseAttribute(GetTypeOf(InvokeAttribute), baseClazz, typeClazz);

        let attrType = baseClazz.name;
        let attrData = new InvokeAttrData();
        attrData.Type = type;
        AddCustomAttribute(attrType, attrData);
    };
}

export function ConfigAttribute() {
    return function (baseClazz) {
        BaseAttribute(GetTypeOf(ConfigAttribute), baseClazz);
    };
}

export function ConsoleHandlerAttribute(mode: string) {
    return function (baseClazz) {
        BaseAttribute(GetTypeOf(ConsoleHandlerAttribute), baseClazz);

        let attrType = baseClazz.name;
        let attrData = new ConsoleHandlerAttrData();
        attrData.Mode = mode;
        AddCustomAttribute(attrType, attrData);
    };
}

export function UniqueIdAttribute<T extends ClassConstructor>(min: number = Number.MIN_SAFE_INTEGER, max: number = Number.MAX_SAFE_INTEGER) {
    return function (baseClazz: T) {
        let valueList: number[] = [];
        for (let key in baseClazz) {
            let value = baseClazz[key];
            // console.log(key, value);
            if (typeof value != "number") {
                throw new Error(`${baseClazz.name}.${key} ${value} is not a number value.`);
            }
            if (typeof value == "number") {
                if (value < min || value > max) {
                    throw new Error(`${baseClazz.name}.${key} ${value} is not between ${min} - ${max}.`);
                }
            }
            if (valueList.indexOf(value) != -1) {
                throw new Error(`${baseClazz.name}.${key} ${value} has been defined.`);
            }
            valueList.push(value);
        }
    };
}

/******************************************************************************************************
 * 
 * 
 * 
 *****************************************************************************************************/

export function GetTypeOf(target: Object | Function) {
    if (!target) {
        return null;
    }
    if (target instanceof Function) {
        return target.name;
    } else if (target["Type"]) {
        return target["Type"];
    } else {
        return target.constructor.name;
    }
}

export function GetParentTypeOf(target: Object | Function) {
    if (!target) {
        return null;
    }
    if (target["Parent"]) {
        return target["Parent"];
    } else {
        return Object.getPrototypeOf(target).name;
    }
}

/******************************************************************************************************
 * 
 * 
 * 
 *****************************************************************************************************/

let friends: Dict<ClassConstructor> = {};
let expands: Dict<ClassConstructor> = {};

export function StaticClass() {
    return function <T extends ClassConstructor>(constructor: T): T {
        let staticClazz = Reflect.construct(constructor, []) as T;
        Object.getOwnPropertyNames(constructor).forEach((key) => {
            const desc = Reflect.getOwnPropertyDescriptor(staticClazz, key);
            let needCopy = false;
            if (key != "prototype") {
                if (!desc) {
                    needCopy = true;
                } else if (desc.writable) {
                    needCopy = true;
                }
            }
            if (needCopy) {
                staticClazz[key] = constructor[key];
            }
        });
        // console.log(staticClazz);
        // console.log(staticClazz.name);
        // console.log(typeof staticClazz);
        // console.log(Object.getOwnPropertyNames(staticClazz));

        // console.log(constructor);
        // console.log(constructor.name);
        // console.log(typeof constructor);
        // console.log(Object.getOwnPropertyNames(constructor));
        return staticClazz as T;
    }
}
export function ExpandOf<T extends ClassConstructor, K extends ClassConstructor>(typeClazz: K) {
    return function (baseClazz: T) {
        // console.log(`ExpandOf =====`);
        // console.log(baseClazz.name);
        // console.log(typeClazz.name);
        // console.log(baseClazz);
        // console.log(Object.getPrototypeOf(baseClazz));

        baseClazz.prototype.entityType = typeClazz.name;
        expands[baseClazz.name] = typeClazz;

        let descriptors = Object.getOwnPropertyDescriptors(baseClazz);
        for (let key in descriptors) {
            let descriptor = descriptors[key];
            if (typeof descriptor.value != "function") {
                continue;
            }
            let descriptorValueName = descriptor.value.name;
            // if (typeClazz.prototype[descriptorValueName]) {
            //     continue;
            // }
            // 返回值为 Promise 的时候，需要 await 或者 then()
            // 其他情况返回的正常对象，如果此时用 async 来修饰 function，则会将结果转化为 Promise。
            // 这样不符合需求，此处不应该加 async。
            typeClazz.prototype[descriptorValueName] = function (...args) {
                return descriptor.value.call(this, this, ...args);
            };
        }
    };
}

export function FriendOf<T extends ClassConstructor, K extends ClassConstructor>(typeClazz: K) {
    return function (baseClazz: T) {
        // console.log(`FriendOf =====`);
        // console.log(baseClazz.name);
        // console.log(typeClazz.name);
        // console.log(baseClazz);
        // console.log(Object.getPrototypeOf(baseClazz));

        baseClazz.prototype.entityType = typeClazz.name;
        friends[baseClazz.name] = typeClazz;
    };
}

export function ChildOf<T extends ClassConstructor, K extends ClassConstructor>(typeClazz: K = null) {
    return function (baseClazz: T) {

    };
}

export function ComponentOf<T extends ClassConstructor, K extends ClassConstructor>(typeClazz: K = null) {
    return function (baseClazz: T) {

    };
}

/******************************************************************************************************
 * 
 * 
 * 
 *****************************************************************************************************/

export function ImplementsInterface<T extends ClassConstructor, K extends ClassConstructor>(typeClazz: K) {
    return function (baseClazz: T) {
        // console.log(`Decorators.ImplementsInterface =====`);
        // console.log(baseClazz.name);
        // console.log(typeClazz.name);

        if (!interfaceCache[typeClazz.name]) {
            interfaceCache[typeClazz.name] = [];
        }
        let cacheIndex = interfaceCache[typeClazz.name].indexOf(baseClazz.name);
        if (cacheIndex != -1) {
            throw new Error(`implements interface twice [${baseClazz.name} implements ${typeClazz.name}]`);
        }
        interfaceCache[typeClazz.name].push(baseClazz.name);
    }
}

/******************************************************************************************************
 * 
 * 
 * 
 *****************************************************************************************************/

let interfaceCache = new Dictionary<string, Array<string>>();

export function IsInstanceOf<T extends Object, K extends ClassConstructor>(clazz: T, typeClazz: K) {
    // console.log(`Decorators.isInstanceOf =====`);
    // console.log(typeClazz.name);

    if (!interfaceCache[typeClazz.name]) {
        return null;
    }
    let cacheIndex = interfaceCache[typeClazz.name].indexOf(clazz.constructor.name);
    if (cacheIndex == -1) {
        return null;
    }
    return new typeClazz();
}

export function IsNotInstanceOf<T extends Object, K extends ClassConstructor>(clazz: T, typeClazz: K) {
    // console.log(`Decorators.isNotInstanceOf =====`);
    // console.log(namedObject.classname);
    // console.log(typeClazz.name);

    if (IsInstanceOf(clazz, typeClazz)) {
        return null;
    }
    return new typeClazz();
}

export function TypeOf<K extends ClassConstructor>(typeClazz: K) {
    return typeClazz.name;
}

/******************************************************************************************************
 * 
 * 
 * 
 *****************************************************************************************************/

export function Sleep(ms: number) {
    return new Promise(resolve => setTimeout(resolve, ms));
}